/**
 * Copyright (c) 2020 Coder League
 * All rights reserved.
 *
 * File：UserService.java
 * History:
 *         2020年12月19日: Initially created, Chrise.
 */
package club.coderleague.cfss.service.user;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

import javax.servlet.http.HttpSession;
import javax.validation.ValidationException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import club.coderleague.cfss.dao.OrganizationDao;
import club.coderleague.cfss.dao.RoleDao;
import club.coderleague.cfss.dao.UserAuthDao;
import club.coderleague.cfss.dao.UserDao;
import club.coderleague.cfss.domain.beans.PagingParameters;
import club.coderleague.cfss.domain.beans.UserSession;
import club.coderleague.cfss.domain.beans.UsersExtension;
import club.coderleague.cfss.domain.beans.VercodeBound;
import club.coderleague.cfss.domain.beans.WebAuthContext;
import club.coderleague.cfss.domain.entities.Organizations;
import club.coderleague.cfss.domain.entities.Roles;
import club.coderleague.cfss.domain.entities.Userauths;
import club.coderleague.cfss.domain.entities.Users;
import club.coderleague.cfss.domain.enums.EntityState;
import club.coderleague.cfss.domain.enums.UserType;
import club.coderleague.cfss.domain.enums.WebAuthScene;
import club.coderleague.cfss.exceptions.RequestProcessInfoException;
import club.coderleague.cfss.exceptions.RequestProcessWarnException;
import club.coderleague.cfss.service.AuthenticationService;
import club.coderleague.cfss.service.interfaces.AliyunInterfaceService;
import club.coderleague.cfss.util.CommonUtil;
import club.coderleague.data.jpa.domain.Page;

/**
 * 用户服务。
 * @author Chrise
 */
@Service
public class UserService implements AuthenticationService {
	private static final Pattern PHONE_REG = Pattern.compile("^1\\d{10}$");
	private static final String DEF_PASSWORD = "123456";
	
	@Autowired
	private UserDao userDao;
	@Autowired
	private OrganizationDao organizationDao;
	@Autowired
	private RoleDao roleDao;
	@Autowired
	private UserAuthDao uaDao;
	@Autowired
	private PasswordEncoder passwordEncoder;
	@Autowired
	private AliyunInterfaceService aiService;
	@Value("${custom.dev-mode:false}")
	private boolean devMode;
	
	/**
	 * @see club.coderleague.cfss.service.AuthenticationService#authenticate(java.lang.String, java.lang.String)
	 */
	@Override
	public boolean authenticate(String username, String password) {
		Users admin = this.userDao.queryAdministrator();
		if (admin != null && admin.getLoginname().equals(username) && this.passwordEncoder.matches(password, admin.getLoginpassword())) return true;
		return false;
	}
	
	/**
	 * 注册管理员。
	 * @author Chrise 2020年12月19日
	 */
	public void execRegisterAdminstrator() {
		if (this.userDao.isAdministratorExists()) return;
		
		Users user = new Users(EntityState.VALID.getValue(), null, null, null, UserType.ADMIN.getValue(), 
			"Adminstrator", null, "admin", this.passwordEncoder.encode("admin"), false, null, null);
		this.userDao.save(user);
	}
	
	/**
	 * 用户登录。
	 * @author Chrise 2019年5月12日
	 * @param username 用户名。
	 * @param userphone 用户电话。
	 * @param password 密码。
	 * @param vericode 验证码。
	 * @param loginmode 登录模式。
	 * @param cachecode 缓存验证码。
	 * @param session 会话对象。
	 * @return 登录结果消息。
	 * @throws Exception 业务处理异常。
	 */
	public String execUserLogin(String username, String userphone, String password, String vericode, boolean loginmode, String cachecode, HttpSession session) 
		throws Exception {
		if (loginmode) {	// 密码登录
			if (!this.devMode && (CommonUtil.isEmpty(vericode) || !vericode.toUpperCase().equals(cachecode))) 
				return "验证码错误！";
			
			if (CommonUtil.isEmpty(username)) return "用户不存在！";
			if (CommonUtil.isEmpty(password)) return "密码错误！";
			
			if (PHONE_REG.matcher(username).matches()) {
				Users user = this.userDao.queryUser(username, true);	// 查找用户
				if (user != null) {
					if (this.passwordEncoder.matches(password, user.getLoginpassword())) {	// 验证密码
						// 设置最近登录时间
						user.setLastlogin(new Date());
						
						if (user.getInitpassword()) {	// 返回强制修改密码
							session.setAttribute(Users.class.getTypeName(), user.getEntityid());
							return "";
						}
						
						// 缓存用户会话
						UserSession us = this.userDao.queryUserSession(username, true);
						session.setAttribute(UserSession.SESSION_KEY, us);
						return null;
					}
					
					return "密码错误！";
				}
			}
			
			Users user = this.userDao.queryUser(username, false);	// 查找用户
			if (user == null) return "用户不存在！";
			
			if (this.passwordEncoder.matches(password, user.getLoginpassword())) {	// 验证密码
				// 设置最近登录时间
				user.setLastlogin(new Date());
				
				if (user.getInitpassword()) {	// 返回强制修改密码
					session.setAttribute(Users.class.getTypeName(), user.getEntityid());
					return "";
				}
				
				// 缓存用户会话
				UserSession us = this.userDao.queryUserSession(username, false);
				session.setAttribute(UserSession.SESSION_KEY, us);
				return null;
			}
			
			return "密码错误！";
		} else {	// 验证码登录
			VercodeBound cached = this.aiService.getCachedVercode(session.getId());
			if (cached == null) return "验证码已失效！";
			
			if (!cached.getPhone().equals(userphone)) return "手机号不匹配！";
			if (!cached.getCode().equals(vericode)) return "验证码错误！";
			
			this.aiService.clearCache(session.getId());
			
			Users user = this.userDao.queryUser(userphone, true);
			if (user == null) return "用户不存在！";
			
			// 设置最近登录时间
			user.setLastlogin(new Date());
			
			// 缓存用户会话
			UserSession us = this.userDao.queryUserSession(user.getEntityid());
			session.setAttribute(UserSession.SESSION_KEY, us);
			return null;
		}
	}
	
	/**
	 * 移动端用户登录。
	 * @author Chrise 2020年12月27日
	 * @param userphone 用户电话。
	 * @param vercode 验证码。
	 * @param boundwx 是否绑定微信。
	 * @param session 会话对象。
	 * @return 登录结果。
	 */
	public Object execMobileUserLogin(String userphone, String vercode, boolean boundwx, HttpSession session) {
		WebAuthContext context = (WebAuthContext)session.getAttribute(WebAuthContext.SESSION_KEY);
		if (context == null) return "微信授权已失效";
		
		VercodeBound cached = this.aiService.getCachedVercode(session.getId());
		if (cached == null) return "验证码已失效";
		
		if (!cached.getPhone().equals(userphone)) return "手机号不匹配";
		if (!cached.getCode().equals(vercode)) return "验证码错误";
		
		this.aiService.clearCache(session.getId());
		
		Users user = this.userDao.queryUser(userphone, true);
		if (user == null) return "用户不存在";
		
		// 设置最近登录时间
		user.setLastlogin(new Date());
		if (boundwx) user.setOpenid(context.getOpenid());
		
		// 查询用户会话
		Boolean isSupplier = null;
		switch (context.getScene()) {
			case SUPPLIER_SYSTEM:
				isSupplier = true;
				break;
			case CYBERCAFE_SYSTEM:
				isSupplier = false;
				break;
			default:
				return "未知错误";	// 理论上不会出现
		}
		UserSession us = this.userDao.queryMobileUserSession(user.getEntityid(), isSupplier.booleanValue());
		
		// 切换会话
		session.removeAttribute(WebAuthContext.SESSION_KEY);
		session.setAttribute(UserSession.SESSION_KEY, us);
		
		return context;
	}
	
	/**
	 * 分页查询用户。
	 * @author Chrise 2020年12月21日
	 * @param pagingparameters 分页参数。
	 * @param isrecycle 是否回收站。
	 * @param keyword 关键字。
	 * @return 分页用户数据。
	 */
	public Page<UsersExtension> findPageByParamsMap(PagingParameters pagingparameters, boolean isrecycle, String keyword) {
		return this.userDao.findPageByParamsMap(pagingparameters, isrecycle, keyword);
	}
	
	/**
	 * 查询所有机构。
	 * @author Chrise 2020年12月21日
	 * @return 机构数据。
	 */
	public List<Organizations> findAllOrganizations() {
		return this.organizationDao.findAllByEntitystate(EntityState.VALID.getValue());
	}
	
	/**
	 * 查询授权角色。
	 * @author Chrise 2020年12月21日
	 * @return 角色数据。
	 */
	public List<Roles> findAuthRoles() {
		return this.roleDao.findAuth();
	}
	
	/**
	 * 根据用户主键查询用户。
	 * @author Chrise 2020年12月21日
	 * @param entityid 用户主键。
	 * @return 用户数据。
	 */
	public UsersExtension findExtensionByEntityid(Long entityid) {
		if (entityid == null) {
			throw new ValidationException("用户主键不能为空");
		}
		return this.userDao.findExtensionByEntityid(entityid);
	}
	
	/**
	 * 新增用户。
	 * @author Chrise 2020年12月21日
	 * @param users 用户数据。
	 * @param roleids 授权角色。
	 */
	public void insert(Users users, String roleids) {
		if (this.userDao.existsByLoginnameOrUserphoneNotEntityid(users.getLoginname(), null, null)) {
			throw new RequestProcessInfoException("登录名已存在");
		}
		users.setEntitystate(EntityState.VALID.getValue());
		users.setUsertype(UserType.NORMAL.getValue());
		users.setLoginpassword(this.passwordEncoder.encode(DEF_PASSWORD));
		users.setInitpassword(true);
		this.userDao.save(users);
		
		if (StringUtils.isNotBlank(roleids)) {
			for (String roleid : roleids.split(",")) {
				this.uaDao.save(new Userauths(EntityState.VALID.getValue(), users.getEntityid(), Long.parseLong(roleid)));
			}
		}
	}
	
	/**
	 * 更新用户。
	 * @author Chrise 2020年12月21日
	 * @param users 用户数据。
	 * @param roleids 授权角色。
	 */
	public void update(Users users, String roleids) {
		if (users.getEntityid() == null) {
			throw new RequestProcessWarnException("用户主键不能为空");
		}
		
		Users usersSource = this.userDao.getOne(users.getEntityid());
		usersSource.setOrgid(users.getOrgid());
		usersSource.setUsername(users.getUsername());
		
		List<Long> validentityids = new ArrayList<>();
		if (StringUtils.isNotBlank(roleids)) {
			for (String roleidstr : roleids.split(",")) {
				Long roleid = Long.parseLong(roleidstr);
				Userauths userauths = this.uaDao.findOneByUseridAndRoleid(users.getEntityid(), roleid);
				if (userauths != null) {
					if (EntityState.INVALID.equalsValue(userauths.getEntitystate())) {
						userauths.setEntitystate(EntityState.VALID.getValue());
					}
					validentityids.add(userauths.getEntityid());
				} else {
					userauths = new Userauths(EntityState.VALID.getValue(), users.getEntityid(), roleid);
					this.uaDao.save(userauths);
					validentityids.add(userauths.getEntityid());
				}
			}
		}
		this.uaDao.updateEntitystateByUseridNotInEntityids(EntityState.INVALID.getValue(), users.getEntityid(), validentityids);
	}
	
	/**
	 * 更新用户状态。
	 * @author Chrise 2020年12月21日
	 * @param entityids 用户主键。
	 * @param entitystate 实体状态。
	 * @param isdistinct 是否去重。
	 */
	public void updateEntitystate(String entityids, Integer entitystate, boolean isdistinct) {
		if (StringUtils.isBlank(entityids)) {
			throw new ValidationException("用户主键不能为空");
		}
		for (String entityid : entityids.split(",")) {
			Users users = this.userDao.getOne(Long.parseLong(entityid));
			if (users == null) {
				continue;
			}
			if (isdistinct && this.userDao.existsByLoginnameOrUserphoneNotEntityid(users.getLoginname(), users.getUserphone(), users.getEntityid())) {
				throw new RequestProcessInfoException("登录名或用户电话已存在");
			}
			users.setEntitystate(entitystate);
		}
	}
	
	/**
	 * 根据用户主键重置密码。
	 * @author Chrise 2020年12月21日
	 * @param entityids 用户主键。
	 */
	public void execResetPassword(String entityids) {
		if (StringUtils.isBlank(entityids)) {
			throw new ValidationException("用户主键不能为空");
		}
		for (String entityid : entityids.split(",")) {
			Users users = this.userDao.getOne(Long.parseLong(entityid));
			if (users == null) {
				continue;
			}
			users.setLoginpassword(this.passwordEncoder.encode(DEF_PASSWORD));
			users.setInitpassword(true);
		}
	}
	
	/**
	 * 检查绑定状态。
	 * @author Chrise 2020年12月23日
	 * @param scene 授权场景。
	 * @param openid 开放标识。
	 * @return 用户会话对象。
	 */
	public UserSession execCheckBound(WebAuthScene scene, String openid) {
		Boolean isSupplier = null;
		switch (scene) {
			case SUPPLIER_SYSTEM:
				isSupplier = true;
				break;
			case CYBERCAFE_SYSTEM:
				isSupplier = false;
				break;
			default:
				return null;
		}
		
		UserSession us = this.userDao.queryMobileUserSession(openid, isSupplier.booleanValue());
		if (us != null) {
			Users user = this.userDao.getOne(us.getId());
			user.setLastlogin(new Date());
		}
		
		return us;
	}
}
